# 导入 QtCore 模块，该模块提供了 Qt 的核心非 GUI 功能
from qtpy import QtCore
# 导入 QtGui 模块，该模块包含了 GUI 应用程序的所有图形功能
from qtpy import QtGui
# 从 QtWidgets 模块导入所有内容，该模块提供了创建桌面风格用户界面的所有组件
from qtpy.QtWidgets import *

# 从 src 的 UI 模块导入 Canvas 相关的通用工具
from src.UI.Canvas.UICommon import *
# 从 src 的 UI 工具模块导入可编辑的样式表
from src.UI.Utils.stylesheet import editableStyleSheet


class CanvasBase(QGraphicsView):
    """
    CanvasBase 类继承自 QGraphicsView，用于创建一个图形视图，
    提供了画布的基本功能，如缩放、平移、绘制背景等。
    """
    # 定义操作模式，初始值为无操作
    _manipulationMode = CanvasManipulationMode.NONE
    # 定义鼠标滚轮的缩放速率
    _mouseWheelZoomRate = 0.0005

    def __init__(self):
        """
        初始化 CanvasBase 类的实例。
        设置视图的各种属性，创建场景，并初始化鼠标位置相关的变量。
        """
        # 调用父类 QGraphicsView 的构造函数
        super(CanvasBase, self).__init__()
        # 记录鼠标按下时选中的项目
        self.pressed_item = None
        # 记录鼠标释放时选中的项目
        self.released_item = None
        # 当前的缩放因子
        self.factor = 1
        # 最小缩放比例
        self._minimum_scale = 0.2
        # 最大缩放比例
        self._maximum_scale = 3.0
        # 设置视图的视口更新模式为全视口更新
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        # 设置变换锚点为鼠标下方
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        # 启用抗锯齿功能 -- 可改为从设置中读取
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        # 启用文本抗锯齿功能
        self.setRenderHint(QtGui.QPainter.TextAntialiasing)
        # 允许视图接受拖放事件
        self.setAcceptDrops(True)
        # 设置视图总是显示工具提示
        self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips)
        # 设置调整大小的锚点为鼠标下方
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)

        # 创建场景并设置到视图中
        self.setScene(self.createScene())

        # 设置场景的矩形区域
        self.scene().setSceneRect(QtCore.QRectF(0, 0, 10, 10))

        # 初始化鼠标按下位置
        self.mousePressPose = QtCore.QPointF(0, 0)
        # 初始化当前鼠标位置
        self.mousePos = QtCore.QPointF(0, 0)
        # 初始化上一次鼠标位置
        self._lastMousePos = QtCore.QPointF(0, 0)

        # 将视图中心设置到场景矩形的中心
        self.centerOn(
            QtCore.QPointF(self.sceneRect().width() / 2, self.sceneRect().height() / 2)
        )

    def createScene(self):
        """
        创建一个新的 QGraphicsScene 并设置其索引方法和场景矩形。

        返回:
            QGraphicsScene: 创建好的场景对象。
        """
        # 创建一个新的场景对象
        scene = QGraphicsScene(self)
        # 设置场景的项目索引方法为无索引
        scene.setItemIndexMethod(QGraphicsScene.NoIndex)
        # 设置场景的矩形区域
        scene.setSceneRect(QtCore.QRectF(0, 0, 10, 10))
        return scene

    def getItemsRect(self, cls=QGraphicsItem, bSelectedOnly=False, bVisibleOnly=True):
        """
        获取满足指定条件的所有项目的边界矩形的并集。

        参数:
            cls (type, 可选): 项目的类型，默认为 QGraphicsItem。
            bSelectedOnly (bool, 可选): 是否只考虑选中的项目，默认为 False。
            bVisibleOnly (bool, 可选): 是否只考虑可见的项目，默认为 True。

        返回:
            QtCore.QRect: 所有满足条件的项目的边界矩形的并集。
        """
        # 存储所有满足条件的项目的边界矩形
        rectangles = []
        # 遍历场景中的所有项目
        for item in self.scene().items():
            if isinstance(item, cls):
                if bVisibleOnly and not item.isVisible():
                    # 如果只考虑可见项目且当前项目不可见，则跳过
                    continue
                if bSelectedOnly and not item.isSelected():
                    # 如果只考虑选中项目且当前项目未选中，则跳过
                    continue

                # 获取项目的场景边界矩形并转换为 QRect 类型
                rect = item.sceneBoundingRect().toRect()
                rectangles.append(rect)

        # 初始化结果矩形
        result = QtCore.QRect()

        # 合并所有矩形
        for r in rectangles:
            result |= r

        return result

    def frameItems(self, items):
        """
        调整视图以显示指定的项目。

        参数:
            items (list): 要显示的项目列表。
        """
        # 初始化包含所有项目的矩形
        rect = QtCore.QRect()
        for i in items:
            # 合并所有项目的场景边界矩形
            rect |= i.sceneBoundingRect().toRect()
        # 调整视图以显示该矩形
        self.frameRect(rect)

    @property
    def manipulationMode(self):
        """
        获取当前的操作模式。

        返回:
            CanvasManipulationMode: 当前的操作模式。
        """
        return self._manipulationMode

    @manipulationMode.setter
    def manipulationMode(self, value):
        """
        设置当前的操作模式，并根据模式设置鼠标光标。

        参数:
            value (CanvasManipulationMode): 要设置的操作模式。
        """
        self._manipulationMode = value
        if value == CanvasManipulationMode.NONE:
            # 无操作模式，设置鼠标光标为箭头
            self.viewport().setCursor(QtCore.Qt.ArrowCursor)
        elif value == CanvasManipulationMode.SELECT:
            # 选择模式，设置鼠标光标为箭头
            self.viewport().setCursor(QtCore.Qt.ArrowCursor)
        elif value == CanvasManipulationMode.PAN:
            # 平移模式，设置鼠标光标为张开的手
            self.viewport().setCursor(QtCore.Qt.OpenHandCursor)
        elif value == CanvasManipulationMode.MOVE:
            # 移动模式，设置鼠标光标为箭头
            self.viewport().setCursor(QtCore.Qt.ArrowCursor)
        elif value == CanvasManipulationMode.ZOOM:
            # 缩放模式，设置鼠标光标为水平调整大小
            self.viewport().setCursor(QtCore.Qt.SizeHorCursor)
        elif value == CanvasManipulationMode.COPY:
            # 复制模式，设置鼠标光标为箭头
            self.viewport().setCursor(QtCore.Qt.ArrowCursor)

    def wheelEvent(self, event):
        """
        处理鼠标滚轮事件，实现视图的缩放和平移。

        参数:
            event (QWheelEvent): 鼠标滚轮事件对象。
        """
        # 将全局鼠标位置转换为场景位置
        scene_pos = self.mapToScene(self.mapFromGlobal(QtGui.QCursor.pos()))

        # 计算缩放因子
        zoomFactor = 1.0 + event.angleDelta().y() * self._mouseWheelZoomRate
        # 执行缩放操作
        self.zoom(zoomFactor)

        # 将场景位置转换为视图位置
        new_pos = self.mapFromScene(scene_pos)
        # 计算鼠标位置的偏移量
        delta = new_pos - self.mapFromGlobal(QtGui.QCursor.pos())
        # 执行平移操作
        self.pan(-delta)

    def zoom(self, scale_factor):
        """
        对视图进行缩放操作，并确保缩放比例在最小和最大缩放比例之间。

        参数:
            scale_factor (float): 缩放因子。
        """
        # 获取当前的缩放因子
        self.factor = self.transform().m22()
        # 计算缩放后的缩放比例
        futureScale = self.factor * scale_factor
        if futureScale <= self._minimum_scale:
            # 如果缩放后的比例小于最小比例，调整缩放因子
            scale_factor = self._minimum_scale / self.factor
        if futureScale >= self._maximum_scale:
            # 如果缩放后的比例大于最大比例，调整缩放因子
            scale_factor = (self._maximum_scale - 0.1) / self.factor
        # 执行缩放操作
        self.scale(scale_factor, scale_factor)

    def frameRect(self, rect):
        """
        调整视图以显示指定的矩形区域。

        参数:
            rect (QtCore.QRect): 要显示的矩形区域。
        """
        if rect is None:
            return
        # 将视图矩形转换为场景矩形
        windowRect = self.mapToScene(self.rect()).boundingRect()

        # 计算需要平移的偏移量，使矩形中心位于视图中心
        delta = windowRect.center() - rect.center()
        delta *= self.currentViewScale()
        # 执行平移操作
        self.pan(delta)

        # 计算缩放比例以适应内容
        ws = windowRect.size()
        # 扩展矩形区域
        rect += QtCore.QMargins(40, 40, 40, 40)
        widthRef = ws.width()
        heightRef = ws.height()
        sx = widthRef / rect.width()
        sy = heightRef / rect.height()
        scale = sx if sy > sx else sy
        # 执行缩放操作
        self.zoom(scale)

        return scale

    def zoomDelta(self, direction):
        """
        根据方向进行缩放操作。

        参数:
            direction (bool): 缩放方向，True 表示放大，False 表示缩小。
        """
        if direction:
            # 放大操作
            self.zoom(1 + 0.1)
        else:
            # 缩小操作
            self.zoom(1 - 0.1)

    def pan(self, delta):
        """
        对场景进行平移操作。

        参数:
            delta (QtCore.QPointF): 平移的偏移量。
        """
        # 获取当前场景的矩形区域
        rect = self.sceneRect()
        # 获取当前的缩放比例
        scale = self.currentViewScale()
        # 计算平移后的 x 坐标
        x = -delta.x() / scale
        # 计算平移后的 y 坐标
        y = -delta.y() / scale
        # 平移场景矩形
        rect.translate(x, y)
        # 设置新的场景矩形
        self.setSceneRect(rect)
        # 更新视图
        self.update()

    def resetScale(self):
        """
        重置视图的缩放比例。
        """
        self.resetMatrix()

    def viewMinimumScale(self):
        """
        获取视图的最小缩放比例。

        返回:
            float: 最小缩放比例。
        """
        return self._minimum_scale

    def viewMaximumScale(self):
        """
        获取视图的最大缩放比例。

        返回:
            float: 最大缩放比例。
        """
        return self._maximum_scale

    def currentViewScale(self):
        """
        获取当前视图的缩放比例。

        返回:
            float: 当前的缩放比例。
        """
        return self.transform().m22()

    def getLodValueFromScale(self, numLods=5, scale=1.0):
        """
        根据缩放比例计算细节层次 (LOD) 值。

        参数:
            numLods (int, 可选): 细节层次的数量，默认为 5。
            scale (float, 可选): 缩放比例，默认为 1.0。

        返回:
            int: 计算得到的细节层次值。
        """
        lod = lerp(
            numLods,
            1,
            GetRangePct(self.viewMinimumScale(), self.viewMaximumScale(), scale),
        )
        return int(round(lod))

    def getLodValueFromCurrentScale(self, numLods=5):
        """
        根据当前视图的缩放比例计算细节层次 (LOD) 值。

        参数:
            numLods (int, 可选): 细节层次的数量，默认为 5。

        返回:
            int: 计算得到的细节层次值。
        """
        return self.getLodValueFromScale(numLods, self.currentViewScale())

    def getCanvasLodValueFromCurrentScale(self):
        """
        根据当前视图的缩放比例计算画布的细节层次 (LOD) 值。

        返回:
            int: 计算得到的画布细节层次值。
        """
        return self.getLodValueFromScale(
            editableStyleSheet().LOD_Number[0], self.currentViewScale()
        )

    def drawBackground(self, painter, rect):
        """
        绘制场景的背景，包括网格和数字。

        参数:
            painter (QtGui.QPainter): 用于绘制的画笔对象。
            rect (QtCore.QRectF): 需要绘制的矩形区域。
        """
        # 调用父类的绘制背景方法
        super(CanvasBase, self).drawBackground(painter, rect)
        # 获取当前画布的细节层次值
        lod = self.getCanvasLodValueFromCurrentScale()
        # 记录当前的边界矩形
        self.boundingRect = rect

        # 填充背景颜色
        painter.fillRect(rect, QtGui.QBrush(editableStyleSheet().CanvasBgColor))

        # 计算网格的起始位置
        left = int(rect.left()) - (
                int(rect.left()) % editableStyleSheet().GridSizeFine[0]
        )
        top = int(rect.top()) - (int(rect.top()) % editableStyleSheet().GridSizeFine[0])

        if editableStyleSheet().DrawGrid[0] >= 1:
            if lod < editableStyleSheet().CanvasSwitch[0]:
                # 绘制细水平网格线
                gridLines = []
                y = float(top)
                while y < float(rect.bottom()):
                    gridLines.append(QtCore.QLineF(rect.left(), y, rect.right(), y))
                    y += editableStyleSheet().GridSizeFine[0]
                painter.setPen(QtGui.QPen(editableStyleSheet().CanvasGridColor, 1))
                painter.drawLines(gridLines)

                # 绘制细垂直网格线
                gridLines = []
                x = float(left)
                while x < float(rect.right()):
                    gridLines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom()))
                    x += editableStyleSheet().GridSizeFine[0]
                painter.setPen(QtGui.QPen(editableStyleSheet().CanvasGridColor, 1))
                painter.drawLines(gridLines)

            # 计算粗网格的起始位置
            left = int(rect.left()) - (
                    int(rect.left()) % editableStyleSheet().GridSizeHuge[0]
            )
            top = int(rect.top()) - (
                    int(rect.top()) % editableStyleSheet().GridSizeHuge[0]
            )

            # 绘制粗垂直网格线
            gridLines = []
            painter.setPen(QtGui.QPen(editableStyleSheet().CanvasGridColorDarker, 1.5))
            x = left
            while x < rect.right():
                gridLines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom()))
                x += editableStyleSheet().GridSizeHuge[0]
            painter.drawLines(gridLines)

            # 绘制粗水平网格线
            gridLines = []
            painter.setPen(QtGui.QPen(editableStyleSheet().CanvasGridColorDarker, 1.5))
            y = top
            while y < rect.bottom():
                gridLines.append(QtCore.QLineF(rect.left(), y, rect.right(), y))
                y += editableStyleSheet().GridSizeHuge[0]
            painter.drawLines(gridLines)

        if editableStyleSheet().DrawNumbers[0] >= 1:
            # 绘制网格数字
            scale = self.currentViewScale()
            f = painter.font()
            # 设置字体大小，确保字体大小不会过小
            f.setPointSize(6 / min(scale, 1))
            f.setFamily("Consolas")
            painter.setFont(f)
            y = float(top)

            while y < float(rect.bottom()):
                y += editableStyleSheet().GridSizeHuge[0]
                inty = int(y)
                if y > top + 30:
                    painter.setPen(
                        QtGui.QPen(
                            editableStyleSheet().CanvasGridColorDarker.lighter(300)
                        )
                    )
                    # 在指定位置绘制数字
                    painter.drawText(rect.left(), y - 1.0, str(inty))

            x = float(left)
            while x < rect.right():
                x += editableStyleSheet().GridSizeHuge[0]
                intx = int(x)
                if x > left + 30:
                    painter.setPen(
                        QtGui.QPen(
                            editableStyleSheet().CanvasGridColorDarker.lighter(300)
                        )
                    )
                    # 在指定位置绘制数字
                    painter.drawText(
                        x, rect.top() + painter.font().pointSize(), str(intx)
                    )